﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;

namespace Cryomancer.StarChart
{
	public class EquitorialCoordinate
	{
		#region Members
		private double ra;
		private double dec;
		#endregion

		#region Properties
		/// <summary>
		/// Gets the Right Ascension of the update event.
		/// </summary>
		public double RightAscension
		{
			get
			{
				return this.ra;
			}
			set
			{
				this.ra = value;
			}
		}

		/// <summary>
		/// Gets the Declination of the update event.
		/// </summary>
		public double Declination
		{
			get
			{
				return this.dec;
			}
			set
			{
				this.dec = value;
			}
		}
		#endregion

		#region C'Tors and object lifetime code
		/// <summary>
		/// Creates a new instance of an astro coordinate
		/// </summary>
		/// <param name="ra">A double value of the RA</param>
		/// <param name="dec">A double value of the Declination</param>
		public EquitorialCoordinate(double ra, double dec) :
			this()
		{
			this.ra  = ra;
			this.dec = dec;
		}

		public EquitorialCoordinate()
		{

		}
		#endregion

		#region Methods
		public override int GetHashCode()
		{
			return base.GetHashCode();
		}

		public static bool operator==(EquitorialCoordinate coord1, EquitorialCoordinate coord2)
		{
			return coord1.Equals(coord2);
		}

		public static bool operator!=(EquitorialCoordinate coord1, EquitorialCoordinate coord2)
		{
			return !coord1.Equals(coord2);
		}

		public override bool Equals(object obj)
		{
			try
			{
				EquitorialCoordinate coord = (EquitorialCoordinate)obj;

				return (this.ra == coord.ra && this.dec == coord.dec);
			}
			catch
			{
				return false;
			}
		}

		public static EquitorialCoordinate FromMinSecFormat(string ra, string dec)
		{
			if (String.IsNullOrEmpty(ra))
			{
				throw new ArgumentNullException("ra");
			}

			if (String.IsNullOrEmpty(dec))
			{
				throw new ArgumentNullException("dec");
			}

			string[] raParts  = ra.Split(':');
			string[] decParts = dec.Split(':');

			if (raParts.Length != 3)
			{
				throw new ArgumentException("RA is not in HH:MM:SS format.");
			}

			if (decParts.Length != 3)
			{
				throw new ArgumentException("DEC is not in DD:MM:SS format.");
			}

			double hourVal;
			double degVal;
			if (!Double.TryParse(raParts[0], out hourVal))
			{
				throw new ArgumentException("RA Hour value is not valid.");
			}

			if (!Double.TryParse(decParts[0], out degVal))
			{
				throw new ArgumentException("Declination Degree value is not valid.");
			}

			double raVal  = hourVal + ComputeMinuteSecond( raParts[1],  raParts[2]);
			double decVal = degVal  + ComputeMinuteSecond(decParts[1], decParts[2]);
			
			return new EquitorialCoordinate(raVal, decVal);
		}

		private static double ComputeMinuteSecond(string min, string sec)
		{
			double minVal;
			double secVal;

			if (!Double.TryParse(min, out minVal))
			{
				throw new ArgumentOutOfRangeException("min");
			}

			if (!Double.TryParse(sec, out secVal))
			{
				throw new ArgumentOutOfRangeException("sec");
			}

			return (minVal / 60) + (secVal / 3600);
		}
		#endregion
	}

	public class AltAzCoordinate
	{
		#region Members
		private double alt;
		private double az;
		#endregion

		#region Properties
		/// <summary>
		/// Gets or sets the altitude, positive up.
		/// </summary>
		public double Altitude
		{
			get
			{
				return this.alt;
			}
			set
			{
				this.alt = value;
			}
		}

		/// <summary>
		/// Gets or sets the azimuth, North oriented.
		/// </summary>
		public double Azimuth
		{
			get
			{
				return this.az;
			}
			set
			{
				this.az = value;
			}
		}

		#endregion

		#region C'Tors and object lifetime code
		/// <summary>
		/// Creates a new instance of the AltAzCoordinate at the given orientation.
		/// </summary>
		/// <param name="alt">The altitude above or below horizontal</param>
		/// <param name="az">The azimuth from North</param>
		public AltAzCoordinate(double alt, double az) :
			this()
		{
			this.alt = alt;
			this.az  = az;
		}

		/// <summary>
		/// Creates a new instance of the AltAzCoordinate initialized to 0/0
		/// </summary>
		public AltAzCoordinate()
		{

		}
		#endregion
	}

	/// <summary>
	/// A Polar coordinate set where R is the length of the vector and Theta is the angle in radians
	/// from the Y axis, from 0 to 2*Pi.
	/// </summary>
	public class PolarCoordinate
	{
		#region Constants
		public static double DegreesPerRadian = 57.2957795;
		public static double TwoPi            = 2 * Math.PI;
		public static double HalfPi           = Math.PI / 2;
		#endregion

		#region Members
		private double r;
		private double theta;
		private double thetaAsDegrees;
		private bool   degreesComputed;
		#endregion

		#region Properties
		/// <summary>
		/// Gets or sets the length of the vector
		/// </summary>
		public double R
		{
			get
			{
				return this.r;
			}
			set
			{
				this.r = value;
			}
		}

		/// <summary>
		/// Gets or sets the angle of the vector, in radians.
		/// </summary>
		public double Theta
		{
			get
			{
				return this.theta;
			}
			set
			{
				SetTheta(value);
			}
		}

		/// <summary>
		/// Gets the theta value in degrees.
		/// </summary>
		public double ThetaAsDegrees
		{
			get
			{
				return ComputeThetaAsDegrees();
			}
		}


		#endregion

		#region C'Tors and object lifetime code
		public PolarCoordinate(Point point) :
			this()
		{
			this.r = Math.Sqrt(point.X * point.X + point.Y * point.Y);

			//Math.Atan returns pos/neg degrees from the X axis.  We want only positive values from the
			//Y axis (that's how I roll).
			double rawTheta = Math.Atan2(point.Y, point.X);
			if (rawTheta == 0.0d)
			{
				this.theta = HalfPi;
			}
			else if (rawTheta < 0)
			{
				this.theta = (rawTheta * -1) + HalfPi;
			}
			else //rawTheta > 0.0d
			{
				if (rawTheta == HalfPi)
				{
					this.theta = 0.0d;
				}
				else if (rawTheta < HalfPi)
				{
					this.theta = HalfPi - rawTheta; 
				}
				else //rawTheta > HalfPi
				{
					this.theta = TwoPi - (rawTheta - HalfPi); 
				}
			}
			/*
			if (point.X == 0)
			{
				if (point.Y >= 0)
				{
					this.theta = 0.0d;
				}
				else
				{
					this.theta = Math.PI;
				}
			}
			else
			{
				double rawTheta = Math.Atan2(point.Y, point.X);
				if (rawTheta == 0.0d)
				{
					this.theta = HalfPi;
				}
				else if (rawTheta < 0.0d)
				{
					this.theta = (rawTheta * -1) + HalfPi;
				}
				else //rawTheta > 0.0d
				{
					if (rawTheta == HalfPi)
					{
						this.theta = 0.0d;
					}
					else if (rawTheta < HalfPi)
					{
						this.theta = HalfPi - rawTheta; 
					}
					else //rawTheta > HalfPi
					{
						this.theta = TwoPi - (rawTheta - HalfPi); 
					}
				}
			}
			*/
		}

		public PolarCoordinate(double r, double theta) :
			this()
		{
			this.r     = r;
			this.theta = theta;
		}

		public PolarCoordinate()
		{
			this.r     = 0.0d;
			this.theta = 0.0d;
		}
		#endregion

		#region Methods

		public Point ToPoint()
		{
			Point retVal = new Point();
			retVal.X = R * Math.Sin(this.theta);
			retVal.Y = R * Math.Cos(this.theta);

			return retVal;
		}

		private void SetTheta(double value)
		{
			if (value > TwoPi)
			{
				this.theta = value - TwoPi;
			}
			else if (value < 0.0d)
			{
				this.theta = value + TwoPi;
			}
			else
			{
				this.theta = value;
			}

			this.degreesComputed = false;
		}

		private double ComputeThetaAsDegrees()
		{
			if (!this.degreesComputed)
			{
				this.thetaAsDegrees  = this.theta * DegreesPerRadian;
				this.degreesComputed = true;
			}

			return this.thetaAsDegrees;
		}
		#endregion
	}
}
